﻿using System;
using System.Globalization;
using System.Reflection;
 
namespace studentSubsidy.Utils
{
    public sealed class DateTimeUtils
    {
        /// <summary> 日期格式 <c>[yyyy-MM-dd]</c> </summary>
        public static readonly DateTimeUtils DATE = new DateTimeUtils("yyyy-MM-dd");
 
        /// <summary> 日期格式 <c>[yyyyMMdd]</c> </summary>
        public static readonly DateTimeUtils DATE_COMPACT = new DateTimeUtils("yyyyMMdd");
 
        /// <summary> 日期格式 <c>[yyyy_MM_dd]</c> </summary>
        public static readonly DateTimeUtils DATE_UNDERLINE = new DateTimeUtils("yyyy_MM_dd");
 
        /// <summary> 时间格式 <c>[HH:mm:ss]</c> </summary>
        public static readonly DateTimeUtils TIME = new DateTimeUtils("HH:mm:ss");
 
        /// <summary> 时间格式 <c>[HHmmss]</c> </summary>
        public static readonly DateTimeUtils TIME_COMPACT = new DateTimeUtils("HHmmss");
 
        /// <summary> 时间格式 <c>[HH_mm_ss]</c> </summary>
        public static readonly DateTimeUtils TIME_UNDERLINE = new DateTimeUtils("HH_mm_ss");
 
        /// <summary> 时间格式 <c>[HH:mm:ss.fff]</c> </summary>
        public static readonly DateTimeUtils TIME_MILLI = new DateTimeUtils("HH:mm:ss.fff");
 
        /// <summary> 时间格式 <c>[HHmmssfff]</c> </summary>
        public static readonly DateTimeUtils TIME_MILLI_COMPACT = new DateTimeUtils("HHmmssfff");
 
        /// <summary> 时间格式 <c>[HH_mm_ss_fff]</c> </summary>
        public static readonly DateTimeUtils TIME_MILLI_UNDERLINE = new DateTimeUtils("HH_mm_ss_fff");
 
        /// <summary> 日期时间格式 <c>[yyyy-MM-dd HH:mm:ss]</c> </summary>
        public static readonly DateTimeUtils DATE_TIME = new DateTimeUtils("yyyy-MM-dd HH:mm:ss");
 
        /// <summary> 日期时间格式 <c>[yyyyMMddHHmmss]</c> </summary>
        public static readonly DateTimeUtils DATE_TIME_COMPACT = new DateTimeUtils("yyyyMMddHHmmss");
 
        /// <summary> 日期时间格式 <c>[yyyy_MM_dd_HH_mm_ss]</c> </summary>
        public static readonly DateTimeUtils DATE_TIME_UNDERLINE = new DateTimeUtils("yyyy_MM_dd_HH_mm_ss");
 
        /// <summary> 日期时间格式 <c>[yyyy-MM-dd HH:mm:ss.fff]</c> </summary>
        public static readonly DateTimeUtils DATE_TIME_MILLI = new DateTimeUtils("yyyy-MM-dd HH:mm:ss.fff");
 
        /// <summary> 日期时间格式 <c>[yyyyMMddHHmmssfff]</c> </summary>
        public static readonly DateTimeUtils DATE_TIME_MILLI_COMPACT = new DateTimeUtils("yyyyMMddHHmmssfff");
 
        /// <summary> 日期时间格式 <c>[yyyy_MM_dd_HH_mm_ss_fff]</c> </summary>
        public static readonly DateTimeUtils DATE_TIME_MILLI_UNDERLINE = new DateTimeUtils("yyyy_MM_dd_HH_mm_ss_fff");
 
 
        // ----------------------------------------------------------------------------------------------
 
 
        private const int DAYS_PER_WEEK = 7;
 
        private const long TICKS_PER_SEC = 10000000L;
 
        private const long TICKS_PER_MILLISEC = 10000L;
 
        private const long UNIX_EPOCH_TICKS = 621355968000000000L;
 
        private readonly string _pattern;
 
        private DateTimeUtils() {}
 
        private DateTimeUtils(string pattern)
        {
            _pattern = pattern;
        }
 
 
        // ----------------------------------------------------------------------------------------------
 
 
        /// <summary>
        /// Formats a date-time object using this formatter.
        /// </summary>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>the formatted string, not null</returns>
        public string Now()
        {
            return Format(DateTime.Now);
        }
 
        /// <summary>
        /// Formats a date-time object using this formatter.
        /// </summary>
        /// <param name="datetime">the date-time object to format, not null</param>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>the formatted string, not null</returns>
        public string Format(DateTime datetime)
        {
            return datetime.ToString(_pattern, DateTimeFormatInfo.InvariantInfo);
        }
 
        /// <summary>
        /// Formats a date-time object using this formatter.
        /// </summary>
        /// <param name="epochSecond">the epoch seconds to format, not null</param>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>the formatted string, not null</returns>
        public string FormatEpochSecond(long epochSecond)
        {
            return Format(OfEpochSecond(epochSecond));
        }
 
        /// <summary>
        /// Formats a date-time object using this formatter.
        /// </summary>
        /// <param name="epochMilli">the epoch milliseconds to format, not null</param>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>the formatted string, not null</returns>
        public string FormatEpochMilli(long epochMilli)
        {
            return Format(OfEpochMilli(epochMilli));
        }
 
 
        // ----------------------------------------------------------------------------------------------
 
 
        /// <summary>
        /// Fully parses the text producing an object of the specified type.
        /// </summary>
        /// <param name="datetime">the text to parse, not null</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <returns>the parsed date-time, not null</returns>
        public DateTime Parse(string datetime)
        {
            return DateTime.ParseExact(datetime, _pattern, CultureInfo.InvariantCulture);
        }
 
        /// <summary>
        /// Fully parses the text producing an object of the specified type.
        /// </summary>
        /// <param name="datetime">the text to parse, not null</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <returns>the parsed time-stamp</returns>
        public long ParseEpochSecond(string datetime)
        {
            return ToEpochSecond(Parse(datetime));
        }
 
        /// <summary>
        /// Fully parses the text producing an object of the specified type.
        /// </summary>
        /// <param name="datetime">the text to parse, not null</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <returns>the parsed time-stamp</returns>
        public long ParseEpochMilli(string datetime)
        {
            return ToEpochMilli(Parse(datetime));
        }
 
 
        // ----------------------------------------------------------------------------------------------
 
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="years">the years to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddYears(string datetime, int years)
        {
            return Format(Parse(datetime).AddYears(years));
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="months">the months to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddMonths(string datetime, int months)
        {
            return Format(Parse(datetime).AddMonths(months));
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="weeks">the weeks to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddWeeks(string datetime, int weeks)
        {
            return AddDays(datetime, weeks * DAYS_PER_WEEK);
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="days">the days to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddDays(string datetime, int days)
        {
            return Format(Parse(datetime).AddDays(days));
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="hours">the hours to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddHours(string datetime, int hours)
        {
            return Format(Parse(datetime).AddHours(hours));
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="minutes">the minutes to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddMinutes(string datetime, int minutes)
        {
            return Format(Parse(datetime).AddMinutes(minutes));
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="seconds">the seconds to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddSeconds(string datetime, int seconds)
        {
            return Format(Parse(datetime).AddSeconds(seconds));
        }
 
        /// <summary>
        /// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="milliseconds">the milliseconds to add, may be negative</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns>based on this date-time with the years added, not null</returns>
        public string AddMilliseconds(string datetime, int milliseconds)
        {
            return Format(Parse(datetime).AddMilliseconds(milliseconds));
        }
 
 
        // ----------------------------------------------------------------------------------------------
 
 
        /// <summary>
        /// Checks if the year is a leap year, according to the ISO proleptic calendar system rules.
        /// This method applies the current rules for leap years across the whole time-line. 
        /// In general, a year is a leap year if it is divisible by four without remainder. 
        /// However, years divisible by 100, are not leap years, with the exception of years divisible by 400 which are.
        /// The calculation is proleptic - applying the same rules into the far future and far past. 
        /// This is historically inaccurate, but is correct for the ISO-8601 standard.
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>true if the year is leap, false otherwise</returns>
        public bool IsLeapYear(string datetime)
        {
            return IsLeapYear(Parse(datetime));
        }
 
        /// <summary>
        /// Checks if this date-time is after the specified date-time.
        /// </summary>
        /// <param name="source">date-time of the String type</param>
        /// <param name="target">the date-time object to compare to</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <returns>true if this date-time is after the specified date-time</returns>
        public bool IsAfter(string source, DateTime target) 
        {
            return Parse(source) > target;
        }
 
        /// <summary>
        /// Checks if this date-time is before the specified date-time.
        /// </summary>
        /// <param name="source">date-time of the String type</param>
        /// <param name="target">the date-time object to compare to</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <returns>true if this date-time is before the specified date-time</returns>
        public bool IsBefore(string source, DateTime target)
        {
            return Parse(source) < target;
        }
 
        /// <summary>
        /// Checks if this date-time is equal to the specified date-time.
        /// </summary>
        /// <param name="source">date-time of the String type</param>
        /// <param name="target">the date-time object to compare to</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <returns>true if this date-time is equal to the specified date-time</returns>
        public bool IsEqual(string source, DateTime target) 
        {
            return Parse(source) == target;
        }
 
        /// <summary>
        /// Change the format of the date display
        /// </summary>
        /// <param name="datetime">date-time of the String type</param>
        /// <param name="pattern">the format of the date display</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns></returns>
        public string Transform(string datetime, string pattern)
        {
            return Parse(datetime).ToString(pattern, DateTimeFormatInfo.InvariantInfo);
        }
 
 
        // ----------------------------------------------------------------------------------------------
 
 
        /// <summary>
        /// Checks if the year is a leap year, according to the ISO proleptic calendar system rules.
        /// This method applies the current rules for leap years across the whole time-line. 
        /// In general, a year is a leap year if it is divisible by four without remainder. 
        /// However, years divisible by 100, are not leap years, with the exception of years divisible by 400 which are.
        /// The calculation is proleptic - applying the same rules into the far future and far past. 
        /// This is historically inaccurate, but is correct for the ISO-8601 standard.
        /// </summary>
        /// <param name="datetime">the date-time object</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>true if the year is leap, false otherwise</returns>
        public static bool IsLeapYear(DateTime datetime)
        {
            //return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0 && year % 3200 != 0);
            return DateTime.IsLeapYear(datetime.Year);
        }
 
        /// <summary>
        /// Convert the epoch seconds to date-time object.
        /// </summary>
        /// <param name="epochSecond">the epoch seconds to convert</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception> 
        /// <returns>the parsed date-time</returns>
        public static DateTime OfEpochSecond(long epochSecond)
        {
            // ticks = 621355968000000000L + epochSecond * 10000000L;
            var ticks = UNIX_EPOCH_TICKS + epochSecond * TICKS_PER_SEC;
            return new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
        }
 
        /// <summary>
        /// Convert the epoch milliseconds to date-time object.
        /// </summary>
        /// <param name="epochMilli">the epoch milliseconds to convert</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception> 
        /// <returns>the parsed date-time</returns>
        public static DateTime OfEpochMilli(long epochMilli)
        {
            // ticks = 621355968000000000L + epochMilli * 10000L;
            var ticks = UNIX_EPOCH_TICKS + epochMilli * TICKS_PER_MILLISEC;
            return new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
        }
 
        /// <summary>
        /// Convert the date-time object to epoch seconds.
        /// </summary>
        /// <param name="datetime">the date-time object to convert, not null</param>
        /// <returns>the parsed time-stamp</returns>
        public static long ToEpochSecond(DateTime datetime)
        {
            // epochSecond = (ticks - 621355968000000000L) / 10000000L;
            return (datetime.ToUniversalTime().Ticks - UNIX_EPOCH_TICKS) / TICKS_PER_SEC;
        }
 
        /// <summary>
        /// Convert the date-time object to epoch milliseconds.
        /// </summary>
        /// <param name="datetime">the date-time object to convert, not null</param>
        /// <returns>the parsed time-stamp</returns>
        public static long ToEpochMilli(DateTime datetime)
        {
            // epochMilli = (ticks - 621355968000000000L) / 10000L;
            return (datetime.ToUniversalTime().Ticks - UNIX_EPOCH_TICKS) / TICKS_PER_MILLISEC;
        }
 
        /// <summary>
        /// Returns the current time in milliseconds. 
        /// Note thatwhile the unit of time of the return value is a millisecond,the granularity 
        /// of the value depends on the underlyingoperating system and may be larger. 
        /// For example, manyoperating systems measure time in units of tens ofmilliseconds.
        /// </summary>
        /// <returns>the difference, measured in milliseconds, betweenthe current time and midnight, January 1, 1970 UTC.</returns>
        public static long CurrentTimeMillis() 
        {
            return ToEpochMilli(DateTime.Now);
        }
 
        /// <summary>
        /// Set the global time pattern for display.
        /// </summary>
        /// <param name="pattern">the time pattern</param>
        public static void SetGlobalPattern(string pattern = "yyyy-MM-dd HH:mm:ss")
        {
            if (DateTimeFormatInfo.CurrentInfo != null)
            {
                var type = DateTimeFormatInfo.CurrentInfo.GetType();
                if (type != null)
                {
                    var field = type.GetField("generalLongTimePattern", BindingFlags.NonPublic | BindingFlags.Instance);
                    if (field != null)
                    {
                        field.SetValue(DateTimeFormatInfo.CurrentInfo, pattern);
                    }
                }
            }
        }
 
    }
}
